/*
*  switchboard.c Create and manage switchboard sessions
*  Copyright (c) 2005 Derek Ingrouville (dergro@users.sourceforge.net)
*
*  This program is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU Library General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include <string.h>

#include "switchboard.h"
#include "msnslp.h"
#include "utility.h"

int msn_switchboard_connect (MsnSwitchboard *sboard, const char *addr, int port);

void cmd_queue_add (MsnSwitchboard *sboard, char *cmd);
void process_cmd_queue (MsnSwitchboard *sboard);

MsnContact *msn_switchboard_find_contact (MsnSwitchboard *sboard, char *passport);
void msn_switchboard_add_contact (MsnSwitchboard *sboard, MsnContact *contact);
void msn_switchboard_remove_contact (MsnSwitchboard *sboard, MsnContact *contact);

void msn_switchboard_setup_callbacks (MsnSwitchboard *sboard);

MsnSwitchboard *
msn_switchboard_new	(MsnSession	*session,
			 unsigned int id,
			 MsnSwitchboardType type)
{
	MsnSwitchboard *sboard = NULL;

	sboard = g_new (MsnSwitchboard, 1);
	sboard->conn = msn_connection_new (session);
	sboard->session = session;
	sboard->authstr = NULL;
	sboard->id = id;
	sboard->type = type;

	sboard->connect_cb = NULL;

	sboard->participants = NULL;
	sboard->send_queue = g_queue_new ();
	sboard->connected = FALSE;

	msn_switchboard_setup_callbacks (sboard);

	msn_session_add_switchboard (session, sboard);

	return sboard;
}

void
msn_switchboard_free (MsnSwitchboard *sboard)
{
	g_return_if_fail (sboard != NULL);

	if (sboard->connected) {
		msn_switchboard_disconnect (sboard);
		msn_connection_free (sboard->conn);
		sboard->conn = NULL;
	}

	if (sboard->send_queue != NULL) {
		g_queue_free (sboard->send_queue);
		sboard->send_queue = NULL;
	}

	if (sboard->participants != NULL) {
		g_list_free (sboard->participants);
		sboard->participants = NULL;
	}

	msn_session_remove_switchboard (sboard->session, sboard);

	g_free (sboard);
}

int
msn_switchboard_connect (MsnSwitchboard *sboard,
			 const char *addr, 
			 int port)
{
	int ret = 0;

	g_return_val_if_fail (sboard != NULL, -1);
	g_return_val_if_fail (addr != NULL, -1);
	g_return_val_if_fail (port > 0, -1);

	if (sboard->connected)
		msn_switchboard_disconnect (sboard);

	ret = msn_connection_connect (sboard->conn, addr, port);

	return ret;
}

int
msn_switchboard_create_session (MsnSwitchboard *sboard,
				const char *addr, 
				int port,
				const char *authstr)
{
	int ret = 0;

	g_return_val_if_fail (sboard != NULL, -1);
	g_return_val_if_fail (addr != NULL, -1);
	g_return_val_if_fail (port > 0, -1);
	g_return_val_if_fail (authstr != NULL, -1);

	ret = msn_switchboard_connect (sboard, addr, port);

	if (ret < 0)
		return -1;

	/* Handshake */
	ret = msn_connection_send_cmd (sboard->conn,
		"USR %i %s %s\r\n",
		sboard->conn->trid,
		sboard->session->acct->passport,
		authstr);

	if (ret < 0)
		msn_connection_disconnect (sboard->conn);
	else
		sboard->authstr = g_strdup (authstr);

	return ret;
}

int
msn_switchboard_join_session (MsnSwitchboard *sboard,
			      const char *addr, 
			      int port,
			      const char *authstr)
{
	int ret = 0;

	g_return_val_if_fail (sboard != NULL, -1);
	g_return_val_if_fail (addr != NULL, -1);
	g_return_val_if_fail (port > 0, -1);
	g_return_val_if_fail (authstr != NULL, -1);

	ret = msn_switchboard_connect (sboard, addr, port);

	if (ret < 0)
		return -1;

	ret = msn_connection_send_cmd (sboard->conn,
		"ANS %i %s %s %i\r\n",
		sboard->conn->trid,
		sboard->session->acct->passport,
		authstr,
		sboard->id);

	if (ret < 0)
		msn_connection_disconnect (sboard->conn);
	else
		sboard->authstr = g_strdup (authstr);

	return ret;
}

void
msn_switchboard_disconnect (MsnSwitchboard *sboard)
{
	GList *list = NULL;

	g_return_if_fail (sboard != NULL);

	if (!sboard->connected)
		return;

	/* Notify other participants that we are leaving */
	msn_connection_send_cmd (sboard->conn, "OUT\r\n");

	msn_connection_disconnect (sboard->conn);

	if (sboard->authstr != NULL) {
		g_free (sboard->authstr);
		sboard->authstr = NULL;
	}

	if (g_queue_get_length (sboard->send_queue) > 0) {
		char *data = NULL;
		while (data = g_queue_pop_tail (sboard->send_queue))
			g_free (data);
	}

	/* Free all participants */
	list = sboard->participants;

	while (list != NULL) {
		MsnContact *tmp = NULL;
		tmp = (MsnContact *) list->data;
		if (tmp == NULL)
			break;
		msn_switchboard_remove_contact (sboard, tmp);
		list = g_list_next (list);
	}


	sboard->connected = FALSE;

	msn_event_add (sboard->conn->events, "disconnected", NULL);
}

void
msn_switchboard_set_connect_cb (MsnSwitchboard *sboard,
				MsnSboardCB connect_cb)
{
	g_return_if_fail (sboard != NULL);
	g_return_if_fail (connect_cb != NULL);

	sboard->connect_cb = connect_cb;
}

int
msn_switchboard_invite_user (MsnSwitchboard *sboard, const char *passport)
{
	char *cmd = NULL;
	int ret = 0;

	g_return_val_if_fail (sboard != NULL, -1);
	g_return_val_if_fail (passport != NULL, -1);

	cmd = g_strdup_printf ("CAL %i %s\r\n",
		sboard->conn->trid,
		passport);

	if (sboard->connected) {
		ret = socket_write (sboard->conn->socket, cmd, strlen (cmd));                 
	}
	else
		cmd_queue_add (sboard, cmd);


	g_free (cmd);

	if (ret > 0)
		return 0;

	return -1;
}

int
msn_switchboard_send_message (MsnSwitchboard *sboard, char *buffer)
{
	char *mime_ver = "MIME-Version: 1.0";
	char *content_type = "Content-Type: text/plain; charset=UTF-8";
	char *format = "X-MMS-IM-Format: FN=Arial; EF=; CO=0; CS=0; PF=0";
	char *message = NULL;
	int length = 0;
	int ret = 0;

	g_return_val_if_fail (sboard != NULL, -1);
	g_return_val_if_fail (buffer != NULL, -1);

	message = msn_message_build_new_string ('N', sboard->conn->trid, buffer, 
											"%s\r\n%s\r\n%s\r\n",
											mime_ver,
											content_type,
											format);
	
	

	length = strlen (message);
	
	/* Messages longer than MSG_MAX_LEN will cause a disconnection */
	if (length > MSG_MAX_LEN) {
		g_free (message);
		return 1;
	}
	
	ret = msn_connection_send_cmd (sboard->conn, "%s", message);
	g_free (message);
	
	return ret;
}

MsnContact *
msn_switchboard_find_contact (MsnSwitchboard *sboard, char *passport)
{
	MsnContact *contact = NULL;
	GList *list = NULL;

	g_return_val_if_fail (sboard != NULL, NULL);
	g_return_val_if_fail (sboard->participants != NULL, NULL);

	list = sboard->participants;

	while (list != NULL) {
		MsnContact *tmp = NULL;
		tmp = (MsnContact *) list->data;
		if (tmp == NULL)
			break;
		if (strcmp (tmp->passport, passport) == 0) {
			contact = tmp;
			break;
		}
		list = g_list_next (list);
	}

	return contact;
}

void
msn_switchboard_add_contact (MsnSwitchboard *sboard, MsnContact *contact)
{
	g_return_if_fail (sboard != NULL);
	g_return_if_fail (contact != NULL);

	sboard->participants = g_list_append (sboard->participants, contact);
	msn_event_add (sboard->conn->events, "contact_joined", contact);
}

void
msn_switchboard_remove_contact (MsnSwitchboard *sboard, MsnContact *contact)
{
	g_return_if_fail (sboard != NULL);
	g_return_if_fail (contact != NULL);

	sboard->participants = g_list_remove (sboard->participants, contact);
	msn_event_add (sboard->conn->events, "contact_left", contact);
}

void
cmd_queue_add (MsnSwitchboard *sboard, char *cmd)
{
	char *tmp = NULL;

	g_return_if_fail (sboard != NULL);
	g_return_if_fail (cmd != NULL);

	tmp = g_strdup  (cmd);

	g_queue_push_head (sboard->send_queue, tmp);
}

void
process_cmd_queue (MsnSwitchboard *sboard)
{
	int ret;
	char *line = NULL;

	g_return_if_fail (sboard != NULL);

	if (sboard->send_queue == NULL)
		return;

	line = g_queue_pop_tail (sboard->send_queue);
	if (line == NULL)
		return;

	ret = socket_write (sboard->conn->socket, line, strlen (line));  
	g_free (line);

	if (ret <= 0)
		g_error ("process_cmd_queue: socket_write did not succeed: ret = %i\n", ret);
}

static int
usr_cmd (MsnSwitchboard *sboard, MsnCommand *cmd)
{
	g_return_val_if_fail (sboard != NULL, -1);
	g_return_val_if_fail (cmd != NULL, -1);

	if (strcmp (cmd->name, "USR") != 0)
		return -1;

	if (strcmp (cmd->params [1], "OK") != 0)
		return -1;

	sboard->connected = TRUE;
	process_cmd_queue (sboard);

	if (sboard->connect_cb != NULL)
		sboard->connect_cb (sboard, sboard->conn->callbacks->event_data);

	return 0;
}

static int
ans_cmd (MsnSwitchboard *sboard, MsnCommand *cmd)
{
	g_return_val_if_fail (sboard != NULL, -1);
	g_return_val_if_fail (cmd != NULL, -1);

	if (strcmp (cmd->name, "ANS") != 0)
		return -1;

	if (strcmp (cmd->params [1], "OK") != 0)
		return -1;

	sboard->connected = TRUE;
	process_cmd_queue (sboard);

	if (sboard->connect_cb != NULL)
		sboard->connect_cb (sboard, sboard->conn->callbacks->event_data);

	return 0;
}

static int
iro_cmd (MsnSwitchboard *sboard, MsnCommand *cmd)
{
	MsnContact *contact = NULL;
	char *passport = NULL;
	char *nickname = NULL;
	char *clientcaps = NULL;
	
	/* TODO: clientcaps */

	/* IRO TRID NUM TOTAL passport nickname clientcaps*/

	g_return_val_if_fail (sboard != NULL, -1);
	g_return_val_if_fail (cmd != NULL, -1);

	if (strcmp (cmd->name, "IRO") != 0)
		return -1;

	passport = url_decode (cmd->params [3]);
	nickname = url_decode (cmd->params [4]);

	/* Check if contact is in our clist */
	contact = msn_clist_find_contact (sboard->session->clist, passport);
	if (contact == NULL) {
		contact = msn_contact_new ();
		msn_contact_set_passport (contact, passport);
		msn_contact_set_nickname (contact, nickname);
	}

	msn_switchboard_add_contact (sboard, contact);

	g_free (passport);
	g_free (nickname);

	return 0;
}

static int
joi_cmd (MsnSwitchboard *sboard, MsnCommand *cmd)
{
	MsnContact *contact = NULL;
	char *passport = NULL;
	char *nickname = NULL;
	char *client_caps = NULL;

	/* TODO: deal with client_caps here */
	/* JOI passport nickname */

	g_return_val_if_fail (sboard != NULL, -1);
	g_return_val_if_fail (cmd != NULL, -1);

	if (strcmp (cmd->name, "JOI") != 0)
		return -1;

	passport = url_decode (cmd->params [0]);
	nickname = url_decode (cmd->params [1]);

	/* Check if contact is in our clist */
	contact = msn_clist_find_contact (sboard->session->clist, passport);
	if (contact == NULL) {
		contact = msn_contact_new ();
		msn_contact_set_passport (contact, passport);
		msn_contact_set_nickname (contact, nickname);
	}

	msn_switchboard_add_contact (sboard, contact);

	g_free (passport);
	g_free (nickname);

	return 0;
}

static int
bye_cmd (MsnSwitchboard *sboard, MsnCommand *cmd)
{
	MsnContact *contact = NULL;
	char *passport = NULL;

	/* Check if there are any other participants */
	g_return_val_if_fail (sboard != NULL, -1);
	g_return_val_if_fail (cmd != NULL, -1);

	passport = cmd->params [0];
	g_assert (passport != NULL);

	contact = msn_switchboard_find_contact (sboard, passport);
	g_assert (contact != NULL);

	msn_switchboard_remove_contact (sboard, contact);

	if (g_list_length (sboard->participants) == 0) {
		/* No one left in the conversation */
		msn_switchboard_disconnect (sboard);
	}

	return 0;
}

static int
msg_cmd (MsnSwitchboard *sboard, MsnCommand *cmd)
{
	MsnCallback func = NULL;
	MsnMessage *msg = NULL;
	int ret = 0;

	g_return_val_if_fail (sboard != NULL, -1);
	g_return_val_if_fail (sboard->connected, -1);
	g_return_val_if_fail (cmd != NULL, -1);
	g_return_val_if_fail (cmd->param_count > 0, -1);
	g_return_val_if_fail (cmd->params[0] != NULL, -1);

	msg = msn_connection_recv_msg (sboard->conn, cmd->command);
	if (msg == NULL)
		return -1;

	func = msn_callbacks_lookup (sboard->conn->callbacks, msg->content_type);
	if (func == NULL) {
		/* Unhandled Command */
		g_message ("Unhandled message type: %s\r\n", msg->content_type);
		return 0;
	}

	ret = func (sboard->conn->callbacks->cmd_data, msg);

	return ret;
}

static int
text_msg (MsnSwitchboard *sboard, MsnMessage *msg)
{
	g_return_val_if_fail (sboard != NULL, -1);
	g_return_val_if_fail (msg != NULL, -1);

	if (msg->body != NULL)
		g_print ("Received: %s\n", msg->body);

	msn_event_add (sboard->conn->events, "message_received", msg);

	return 0;
}

static int
typing_msg (MsnSwitchboard *sboard, MsnMessage *msg)
{
	g_return_val_if_fail (sboard != NULL, -1);
	g_return_val_if_fail (msg != NULL, -1);

	msn_event_add (sboard->conn->events, "typing_notification", msg);

	return 0;
}

static int
p2p_msg (MsnSwitchboard *sboard, MsnMessage *msg)
{
	MsnSLPMsg *slpmsg = NULL;
	g_return_val_if_fail (sboard != NULL, -1);
	g_return_val_if_fail (msg != NULL, -1);

	if (msg->body != NULL)
		g_print ("Received: %s\n", msg->body);

	slpmsg = msn_slpmsg_new_from_msg (msg);
	
	return 0;
}

void
msn_switchboard_setup_callbacks (MsnSwitchboard *sboard)
{
	MsnCallbacks *cbs = NULL;

	g_return_if_fail (sboard != NULL);

	cbs = sboard->conn->callbacks;
	g_return_if_fail (cbs != NULL);

	msn_callbacks_set_cmd_data (cbs, sboard);

	msn_callbacks_add_cmd (cbs, "USR", usr_cmd);
	msn_callbacks_add_cmd (cbs, "ANS", ans_cmd);
	msn_callbacks_add_cmd (cbs, "IRO", iro_cmd);
	msn_callbacks_add_cmd (cbs, "JOI", joi_cmd);
	msn_callbacks_add_cmd (cbs, "BYE", bye_cmd);

	/* Message callbacks */
	msn_callbacks_add_cmd (cbs, "MSG", msg_cmd);
	msn_callbacks_add_msg (cbs, "text/plain", text_msg);
	msn_callbacks_add_msg (cbs, "text/x-msmsgscontrol", typing_msg);
	msn_callbacks_add_msg (cbs, "application/x-msnmsgrp2p", p2p_msg);
}
